SpringBoot+Shiro+Jwt实现根据链接中参数进行权限控制

序号:03

业务需求

  实现一个论坛系统,根据用户组,来区分用户可访问的模块,用户访问无权限访问的文章时,给出相应提示。

实现思路

  Shiro是通过在AuthorizingRealm中获取当前用户权限(这里为可访问文章类型id),保存在SimpleAuthorizationInfo中,然后通过subject.isPermitted(文章类型id)来判断是否有权限访问;
  所以可以在doGetAuthorizationInfo中根据token获取到用户信息,从数据库中联合查询到可以访问的文章类型id,添加到用户权限中,isAccessAllowed中获取用户访问链接中的参数,调用 subject.isPermitted(id),判断是否拥有该权限。

注意点:

  subject.isPermitted(id)调用前一定要先调用subject.login(token);方法,不然在isPermitted时不会进行授权

重点代码

自定义过滤器,重写isAccessAllowed方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class JWTFilter extends AuthorizationFilter {
...
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
logger.info("-----isAccessAllowed----");
if (isLoginAttempt(request, response)) {
try {
executeLogin(request, response);
} catch (Exception e) {
response401(response);
}
}

String id = request.getParameter("id");
Subject subject = getSubject(request,response);

boolean flag = subject.isPermitted(id);
return flag;
}
...
}

自定义realm,重写doGetAuthorizationInfo方法。

授权代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class CustomRealm extends AuthorizingRealm{
...
/**
* 获取权限
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
logger.info("开始鉴权------------doGetAuthorizationInfo");
User user = (User) principalCollection.getPrimaryPrincipal();

List<String> userRoles = new ArrayList<String>();
Set<String> categoryIds = new HashSet<>();
QueryWrapper queryUserWrapper = new QueryWrapper();
queryUserWrapper.eq("uuid",user.getUuid());
user = userService.getOne(queryUserWrapper);
if(null != user){

Ugroup ugroup = ugroupService.getById(user.getGroupId());

userRoles.add(ugroup.getName());

QueryWrapper queryWrapper = new QueryWrapper();
queryWrapper.eq("idx_group_id",ugroup.getId());
List<GroupCategory> groupCategories = groupCategoryService.list(queryWrapper);
if (groupCategories.size() > 0){
for (GroupCategory groupCategory : groupCategories){
categoryIds.add(groupCategory.getCategoryId().toString());
}
}

}else{
throw new AuthorizationException();
}
//为当前用户设置角色和权限
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
authorizationInfo.addRoles(userRoles);
authorizationInfo.addStringPermissions(categoryIds);
return authorizationInfo;
}
...
}

全部代码

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!--aop-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

<!--jwt-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.7.0</version>
</dependency>
<!-- Shiro-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>

CustomRealm.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
package com.zyc.shiro;


import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.zyc.constant.CommonConstant;
import com.zyc.dao.GroupCategoryMapper;
import com.zyc.dao.UgroupMapper;
import com.zyc.entity.GroupCategory;
import com.zyc.entity.Ugroup;
import com.zyc.entity.User;
import com.zyc.redis.JwtRedisDAO;
import com.zyc.service.GroupCategoryService;
import com.zyc.service.UgroupService;
import com.zyc.service.UserService;
import com.zyc.util.JWTUtils;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.*;

/**
* 鉴权
*/
public class CustomRealm extends AuthorizingRealm {

static Logger logger = LoggerFactory.getLogger(CustomRealm.class);

@Autowired
private UgroupService ugroupService;

@Autowired
private GroupCategoryService groupCategoryService;

@Autowired
private UserService userService;

@Autowired
private JwtRedisDAO jwtRedisDAO;



/**
* 必须重写此方法,不然Shiro会报错
*/
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof JWTToken;
}

/**
* 获取权限
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
logger.info("开始鉴权------------doGetAuthorizationInfo");
User user = (User) principalCollection.getPrimaryPrincipal();

List<String> userRoles = new ArrayList<String>();
Set<String> categoryIds = new HashSet<>();
QueryWrapper queryUserWrapper = new QueryWrapper();
queryUserWrapper.eq("uuid",user.getUuid());
user = userService.getOne(queryUserWrapper);
if(null != user){

Ugroup ugroup = ugroupService.getById(user.getGroupId());

userRoles.add(ugroup.getName());

QueryWrapper queryWrapper = new QueryWrapper();
queryWrapper.eq("idx_group_id",ugroup.getId());
List<GroupCategory> groupCategories = groupCategoryService.list(queryWrapper);
if (groupCategories.size() > 0){
for (GroupCategory groupCategory : groupCategories){
categoryIds.add(groupCategory.getCategoryId().toString());
}
}

}else{
throw new AuthorizationException();
}
//为当前用户设置角色和权限
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
authorizationInfo.addRoles(userRoles);
authorizationInfo.addStringPermissions(categoryIds);
return authorizationInfo;
}

@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) {
logger.info("CustomRealm------------------doGetAuthenticationInfo");
Map claims = JWTUtils.getClaims(CommonConstant.JWT_SECRET, (String) authenticationToken.getPrincipal());

if (claims == null) {
//没找到帐号
throw new UnknownAccountException();
}

String uuid = (String) claims.get("uuid");
QueryWrapper queryWrapper = new QueryWrapper();
queryWrapper.eq("uuid",uuid);
User user = userService.getOne(queryWrapper);
//logger.info(user.toString());
if(null == user){
throw new UnknownAccountException();
}

String token = jwtRedisDAO.get(CommonConstant.ADMIN_JWT_PREFIX + uuid);
//logger.info("token {}", authenticationToken.getPrincipal());
if (token == null || !token.equals(authenticationToken.getPrincipal())) {
throw new AuthorizationException();
}

//交给AuthenticatingRealm使用CredentialsMatcher进行密码匹配,可以自定义实现
return new SimpleAuthenticationInfo(
//用户信息
user,
authenticationToken.getPrincipal(),
//realm name
getName()
);

}
}

JWTFilter.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
package com.zyc.shiro;

import com.alibaba.fastjson.JSON;
import com.zyc.exception.enums.ErrorEnums;
import com.zyc.vo.RestResult;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import org.apache.shiro.web.filter.authz.AuthorizationFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

public class JWTFilter extends AuthorizationFilter {

private static Logger logger = LoggerFactory.getLogger(JWTFilter.class);

/**
* 判断用户是否想要登入。
* 检测header里面是否包含Authorization字段即可
*/
//@Override
protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
logger.info("-----isLoginAttempt----");
HttpServletRequest req = (HttpServletRequest) request;
String authorization = req.getHeader("Authorization");
return authorization != null;
}

/**
* 实现用户登录
*/
//@Override
protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
logger.info("-----executeLogin----");

HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String authorization = httpServletRequest.getHeader("Authorization");

JWTToken token = new JWTToken(authorization);
// 提交给realm进行登入,如果错误他会抛出异常并被捕获
getSubject(request, response).login(token);
// 如果没有抛出异常则代表登入成功,返回true
return true;
}

/**
* 该注释并非现在方法的注释,是以前版本的,不要受影响,写在这里只是提醒还有其他写法
*
* 这里我们详细说明下为什么最终返回的都是true,即允许访问
* 例如我们提供一个地址 GET /article
* 登入用户和游客看到的内容是不同的
* 如果在这里返回了false,请求会被直接拦截,用户看不到任何东西
* 所以我们在这里返回true,Controller中可以通过 subject.isAuthenticated() 来判断用户是否登入
* 如果有些资源只有登入用户才能访问,我们只需要在方法上面加上 @RequiresAuthentication 注解即可
* 但是这样做有一个缺点,就是不能够对GET,POST等请求进行分别过滤鉴权(因为我们重写了官方的方法),但实际上对应用影响不大
*/
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
logger.info("-----isAccessAllowed----");
if (isLoginAttempt(request, response)) {
try {
executeLogin(request, response);
} catch (Exception e) {
response401(response);
}
}

String id = request.getParameter("id");
Subject subject = getSubject(request,response);

boolean flag = subject.isPermitted(id);
return flag;
}

/**
* 对跨域提供支持
*/
@Override
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
logger.info("-----preHandle----");

HttpServletRequest httpServletRequest = (HttpServletRequest) request;
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));
httpServletResponse.setHeader("Access-Control-Allow-Methods", httpServletRequest.getMethod());
httpServletResponse.setHeader("Access-Control-Allow-Credentials", "true");
httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));

// 跨域时会首先发送一个option请求,这里我们给option请求直接返回正常状态
if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
httpServletResponse.setStatus(HttpStatus.OK.value());
return true;
}
return super.preHandle(request, response);
}

/**
* onAccessDenied:表示当访问拒绝时是否已经处理了;如果返回 true 表示需要继续处理;如果返回 false 表示该拦截器实例已经处理了,将直接返回即可。
* @param servletRequest
* @param servletResponse
* @return
* @throws Exception
*/
@Override
protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) {
logger.info("当 isAccessAllowed 返回 false 的时候,才会执行 method onAccessDenied ");

try {
HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse;
RestResult result = new RestResult();
result.setCode(ErrorEnums.PERMISSION_DENIED.getCode());
result.setMsg(ErrorEnums.PERMISSION_DENIED.getMsg());
httpServletResponse.setContentType("application/json;charset=UTF-8");
PrintWriter out = httpServletResponse.getWriter();
out.print(JSON.toJSONString(result));
out.flush();
out.close();
} catch (IOException e) {
logger.error(e.getMessage());
}

// 返回 false 表示已经处理,例如页面跳转啥的,表示不在走以下的拦截器了(如果还有配置的话)
return false;
}

/**
* 非法请求返回code401
*/
private void response401(ServletResponse resp) {
logger.info("-----response401----");

try {
HttpServletResponse httpServletResponse = (HttpServletResponse) resp;

RestResult result = new RestResult();
result.setCode(ErrorEnums.TOKEN_MISS.getCode());
result.setMsg(ErrorEnums.TOKEN_MISS.getMsg());
httpServletResponse.setContentType("application/json;charset=UTF-8");
PrintWriter out = httpServletResponse.getWriter();
out.print(JSON.toJSONString(result));

out.flush();
out.close();
} catch (IOException e) {
logger.error(e.getMessage());
}
}
}

JWTToken.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.zyc.shiro;

import org.apache.shiro.authc.AuthenticationToken;

public class JWTToken implements AuthenticationToken {

private String token;

public JWTToken(String token) {
this.token = token;
}

@Override
public Object getPrincipal() {
return token;
}

@Override
public Object getCredentials() {
return getPrincipal();
}
}

ShiroConfigurer.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
package com.zyc;

import com.zyc.shiro.*;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authz.ModularRealmAuthorizer;
import org.apache.shiro.authz.permission.PermissionResolver;
import org.apache.shiro.authz.permission.RolePermissionResolver;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
import org.apache.shiro.mgt.DefaultSubjectDAO;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;

import java.util.*;

@Configuration
public class ShiroConfigurer {

private static final Logger logger = LoggerFactory.getLogger(ShiroConfigurer.class);

/**
* Shiro的Web过滤器Factory 命名:shiroFilter<br />
*
* @param securityManager
* @return
*/
@Bean(name = "shiroFilter")
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
logger.info("注入Shiro的Web过滤器-->shiroFilter {}", ShiroFilterFactoryBean.class);
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

// Shiro的核心安全接口,这个属性是必须的
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 要求登录时的链接(可根据项目的URL进行替换),非必须的属性,默认会自动寻找Web工程根目录下的"/login.jsp"页面
// shiroFilterFactoryBean.setLoginUrl("/login")
// 登录成功后要跳转的连接,逻辑也可以自定义,例如返回上次请求的页面
// shiroFilterFactoryBean.setSuccessUrl("/index")
// 用户访问未对其授权的资源时,所显示的连接
// shiroFilterFactoryBean.setUnauthorizedUrl("/pages/403")
/* 定义shiro过滤器,例如实现自定义的FormAuthenticationFilter,需要继承FormAuthenticationFilter
**本例中暂不自定义实现,在下一节实现验证码的例子中体现
*/

/*定义shiro过滤链 Map结构
* Map中key(xml中是指value值)的第一个'/'代表的路径是相对于HttpServletRequest.getContextPath()的值来的
* anon:它对应的过滤器里面是空的,什么都没做,这里.do和.jsp后面的*表示参数,比方说login.jsp?main这种
* authc:该过滤器下的页面必须验证后才能访问,它是Shiro内置的一个拦截器org.apache.shiro.web.filter.authc.FormAuthenticationFilter
*/
// 添加自己的过滤器并且取名为jwt
Map filterMap = new HashMap();
filterMap.put("jwt", new JWTFilter());
shiroFilterFactoryBean.setFilters(filterMap);


Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
// 配置退出过滤器,其中的具体的退出代码Shiro已经替我们实现了

// <!-- 过滤链定义,从上向下顺序执行,一般将 /**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了
// <!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->
// filterChainDefinitionMap.put("/webui/**", "anon")
// filterChainDefinitionMap.put("/webjars/**", "anon")
//filterChainDefinitionMap.put("/sys/login", "anon");
//filterChainDefinitionMap.put("/sys/logout", "anon");
//filterChainDefinitionMap.put("/token/callback", "anon");
//filterChainDefinitionMap.put("/dist", "anon");
//filterChainDefinitionMap.put("/download/excel", "anon");
//登陆相关api不需要被过滤器拦截
filterChainDefinitionMap.put("/user/login", "anon");
filterChainDefinitionMap.put("/**", "authc");
// 所有请求通过JWT Filter
filterChainDefinitionMap.put("/**", "jwt");

shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

return shiroFilterFactoryBean;
}

@Bean
public JWTFilter jwtFilter() {
return new JWTFilter();
}

/**
* Shiro Realm 继承自AuthorizingRealm的自定义Realm,即指定Shiro验证用户登录的类为自定义的
*
* @param
* @return managerRealm
*/
@Bean
public CustomRealm userRealm() {
CustomRealm userRealm = new CustomRealm();
// 告诉realm,使用credentialsMatcher加密算法类来验证密文
// userRealm.setCredentialsMatcher(hashedCredentialsMatcher())
userRealm.setCachingEnabled(false);
//自定义权限解析器
return userRealm;
}

/**
* 不指定名字的话,自动创建一个方法名第一个字母小写的bean
*
* @return
* @Bean(name = "securityManager")
*/
@Bean
public SecurityManager securityManager() {
logger.info("注入Shiro的Web过滤器-->securityManager {}", ShiroFilterFactoryBean.class);
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(userRealm());
DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
// 关闭自带session
DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
securityManager.setSubjectDAO(subjectDAO);
return securityManager;
}


/**
* 凭证匹配器
* (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了
* 所以我们需要修改下doGetAuthenticationInfo中的代码;
* )
* 可以扩展凭证匹配器,实现 输入密码错误次数后锁定等功能,下一次
*
* @return
*/
@Bean(name = "credentialsMatcher")
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("SHA-512");
//散列的次数,比如散列两次,相当于 md5(md5(""))
hashedCredentialsMatcher.setHashIterations(2);
//storedCredentialsHexEncoded默认是true,此时用的是密码加密用的是Hex编码;false时用Base64编码
hashedCredentialsMatcher.setStoredCredentialsHexEncoded(true);
return hashedCredentialsMatcher;
}

/**
* Shiro生命周期处理器
*
* @return
*/
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}

/**
* 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证
* 配置以下两个bean(DefaultAdvisorAutoProxyCreator(可选)和AuthorizationAttributeSourceAdvisor)即可实现此功能
*
* @return
*/
@Bean
@DependsOn({"lifecycleBeanPostProcessor"})
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
advisorAutoProxyCreator.setProxyTargetClass(true);
advisorAutoProxyCreator.setUsePrefix(true);
return advisorAutoProxyCreator;
}

@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
return authorizationAttributeSourceAdvisor;
}

}

SpringBoot+Shiro+Jwt实现根据链接中参数进行权限控制

http://leofitz1024.github.io/2020/04/12/Spring Boot/03/

作者

xchen

发布于

2020-04-12

更新于

2021-11-22

评论